package away3d.cameras.lenses ;


import away3d.errors.AbstractMethodError ;
import flash.geom.Matrix3D ;
import flash.geom.Vector3D ;



/**
 * An abstract base class for all lens classes. Lens objects provides a projection matrix that transforms 3D geometry to normalized homogeneous coordinates.
 */
class LensBase
{
    
    private  var _matrix                : Matrix3D ;
    private  var _near                  : Float ;
    private  var _far                   : Float ;
    private  var _aspectRatio           : Float ;
    private var _matrixInvalid          : Bool ;
    private  var _frustumCorners        : Vector ;
    // todo: consider signals instead, single callback is dirty
    public var onInvalidateMatrix       : Void -> Void ;
    private var _unprojection           : Matrix3D ;
    private var _unprojectionInvalid    : Bool ;

    /**
     * Creates a new LensBase object.
     */
    public function new()
    {
        
        _matrix                 = new Matrix3D() ;
        _near                   = 20 ;
        _far                    = 3000 ;
        _aspectRatio            = 1 ;
        _matrixInvalid          = true ;
        _frustumCorners         = new Vector( 8*3, true ) ;
        _unprojection           = new Matrix3D() ;
        _unprojectionInvalid    = true ;
        
    }
    
    
    /**
     * Retrieves the corner points of the lens frustum.
     */
    private function get_frustumCorners() : Vector<Float>
    {
        
        return _frustumCorners ;
        
    }
    
    
    public var matrix( get_matrix, null ) : Matrix3D ;
    
    
    /**
     * The projection matrix that transforms 3D geometry to normalized homogeneous coordinates.
     */
    private function get_matrix() : Matrix3D
    {
        
        if( _matrixInvalid )
        {
            
            updateMatrix() ;
            _matrixInvalid = false ;
            
        }
        
        return _matrix ;
        
    }
    
    
    public var near( get_near, set_near ) : Float ;
    
    
    /**
     * The distance to the near plane of the frustum. Anything behind near plane will not be rendered.
     */
    private function get_near() : Float
    {
        
        return _near ;
        
    }
    
    
    private function set_near( value : Float ) : Float
    {
        
        if( value == _near ) return value ;
        _near = value ;
        invalidateMatrix() ;
        return value ;
        
    }
    
    
    public var far( get_far, set_far ) : Float ;
    
    
    /**
     * The distance to the far plane of the frustum. Anything beyond the far plane will not be rendered.
     */
    private function get_far() : Float
    {
        
        return _far ;
        
    }
    
    
    private function set_far( value : Float ) : Float
    {
        
        if( value == _far ) return value ;
        _far = value ;
        invalidateMatrix() ;
        return value ;
        
    }
    
    
    public function unproject( mX:Float, mY:Float, mZ : Float ):Vector3D
    {
        
        if( _unprojectionInvalid )
        {
            
            _unprojection.copyFrom( matrix ) ;
            _unprojection.invert() ;
            _unprojectionInvalid = false ;
            
        }
        
        var v   = new Vector3D( mX, -mY, mZ, 1.0 ) ;
        v       = _unprojection.transformVector( v ) ;
        var inv = 1/v.w ;
        v.x     *= inv ;
        v.y     *= inv ;
        v.z     *= inv ;
        v.w     = 1.0 ;
        
        return v ;
        
    }
    
    
    /**
     * The aspect ratio ( width/height ) of the view. Set by the renderer.
     * @private
     */
    public function get aspectRatio() : Float
    {
        return _aspectRatio ;
    }
    
    
    public function set aspectRatio( value : Float )
    {
        if( _aspectRatio == value ) return value ;
        _aspectRatio = value ;
        invalidateMatrix() ;
        return value ;
        
    }
    
    
    /**
     * Invalidates the projection matrix, which will cause it to be updated on the next request.
     */
    private function invalidateMatrix()
    {
        
        _matrixInvalid          = true ;
        _unprojectionInvalid    = true ;
        // notify the camera that the lens matrix is changing. this will mark the 
        // viewProjectionMatrix in the camera as invalid, and force the matrix to
        // be re-queried from the lens, and therefore rebuilt.
        if( onInvalidateMatrix != null ) onInvalidateMatrix() ;
    }

    /**
     * Updates the matrix
     */
    private function updateMatrix()
    {
        
        throw new AbstractMethodError() ;
        
    }
    
}